package cn.agiledata.bank.common.util;

import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.math.BigInteger;
import java.security.KeyFactory;
import java.security.PrivateKey;
import java.security.SecureRandom;
import java.security.Signature;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.security.spec.KeySpec;
import java.security.spec.RSAPrivateCrtKeySpec;

import org.bouncycastle.asn1.ASN1InputStream;
import org.bouncycastle.asn1.ASN1OctetString;
import org.bouncycastle.asn1.ASN1Sequence;
import org.bouncycastle.asn1.ASN1TaggedObject;
import org.bouncycastle.asn1.DERInteger;
import org.bouncycastle.asn1.DERObject;
import org.bouncycastle.asn1.DERObjectIdentifier;
import org.bouncycastle.asn1.DEROctetString;
import org.bouncycastle.asn1.pkcs.AuthenticatedSafe;
import org.bouncycastle.asn1.pkcs.CertBag;
import org.bouncycastle.asn1.pkcs.ContentInfo;
import org.bouncycastle.asn1.pkcs.EncryptedData;
import org.bouncycastle.asn1.pkcs.EncryptedPrivateKeyInfo;
import org.bouncycastle.asn1.pkcs.PKCS12PBEParams;
import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
import org.bouncycastle.asn1.pkcs.Pfx;
import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
import org.bouncycastle.asn1.pkcs.SafeBag;
import org.bouncycastle.asn1.pkcs.SignedData;
import org.bouncycastle.asn1.x509.X509CertificateStructure;
import org.bouncycastle.crypto.BlockCipher;
import org.bouncycastle.crypto.BufferedBlockCipher;
import org.bouncycastle.crypto.CipherParameters;
import org.bouncycastle.crypto.PBEParametersGenerator;
import org.bouncycastle.crypto.digests.SHA1Digest;
import org.bouncycastle.crypto.engines.DESedeEngine;
import org.bouncycastle.crypto.engines.RC2Engine;
import org.bouncycastle.crypto.generators.PKCS12ParametersGenerator;
import org.bouncycastle.crypto.modes.CBCBlockCipher;
import org.bouncycastle.crypto.paddings.PaddedBufferedBlockCipher;
import org.bouncycastle.crypto.params.DESParameters;
import org.bouncycastle.crypto.params.KeyParameter;
import org.bouncycastle.crypto.params.ParametersWithIV;
import org.bouncycastle.crypto.params.ParametersWithRandom;
import org.bouncycastle.util.encoders.Base64;

import ccit.security.bssp.CAUtility;

public class PKCS12Util {

	public static final String XML_ELEMENT_NAME = "X509PKCS12";
	private X509Certificate cert = null;
	private java.security.PrivateKey privateKey = null;

	private byte[] codeData(boolean encrypt, byte[] data,
			PKCS12PBEParams pbeParams, char[] password, BlockCipher cipher,
			int keySize) throws IOException {
		byte[] my_out;

		try {
			BufferedBlockCipher my_cipher = new PaddedBufferedBlockCipher(
					new CBCBlockCipher(cipher));
			CipherParameters my_param = makePBEParameters(password, pbeParams,
					my_cipher.getUnderlyingCipher().getAlgorithmName(),
					keySize, 64);

			my_param = new ParametersWithRandom(my_param, new SecureRandom());
			my_cipher.init(encrypt, my_param);

			byte[] my_input = data;
			int my_inputlen = my_input.length;
			int my_len = 0;
			byte[] my_tmp = new byte[my_cipher.getOutputSize(my_inputlen)];

			if (my_inputlen != 0) {
				my_len = my_cipher.processBytes(my_input, 0, my_inputlen,
						my_tmp, 0);

			}
			try {
				my_len += my_cipher.doFinal(my_tmp, my_len);
			} catch (Exception e) {
			}

			my_out = new byte[my_len];
			System.arraycopy(my_tmp, 0, my_out, 0, my_len);
		} catch (Exception e) {
			throw new IOException("exception encrypting data - " + e.toString());
		}

		return my_out;
	}

	private CipherParameters makePBEParameters(char[] password,
			PKCS12PBEParams pbeParams, String targetAlgorithm, int keySize,
			int ivSize) {
		PBEParametersGenerator generator = makePBEGenerator();
		byte[] key = PBEParametersGenerator.PKCS12PasswordToBytes(password);
		CipherParameters param;
		generator.init(key, pbeParams.getIV(), pbeParams.getIterations()
				.intValue());
		if (ivSize != 0) {
			param = generator.generateDerivedParameters(keySize, ivSize);
		} else {
			param = generator.generateDerivedParameters(keySize);
		}

		if (targetAlgorithm.startsWith("DES")) {
			if (param instanceof ParametersWithIV) {
				KeyParameter kParam = (KeyParameter) ((ParametersWithIV) param)
						.getParameters();
				DESParameters.setOddParity(kParam.getKey());
			} else {
				KeyParameter kParam = (KeyParameter) param;
				DESParameters.setOddParity(kParam.getKey());
			}
		}

		for (int i = 0; i != key.length; i++) {
			key[i] = 0;
		}

		return param;
	}

	private PBEParametersGenerator makePBEGenerator() {
		return new PKCS12ParametersGenerator(new SHA1Digest());
	}

	public void parseP12(byte[] p12, String password) throws Exception {
		ByteArrayInputStream bais = new ByteArrayInputStream(p12);
		ASN1InputStream asn1is = new ASN1InputStream(bais);
		bais.close();
		DERObject derO = asn1is.readObject();
		asn1is.close();
		ASN1Sequence asn1seq = (ASN1Sequence) derO;
		Pfx bag = new Pfx(asn1seq);
		ContentInfo info = bag.getAuthSafe();
		// MacData mData = bag.getMacData();
		// DigestInfo dInfo = mData.getMac();
		// AlgorithmIdentifier algId = dInfo.getAlgorithmId();
		// byte[] salt = mData.getSalt();
		// int itCount = mData.getIterationCount().intValue();
		asn1is = new ASN1InputStream(new ByteArrayInputStream(
				((ASN1OctetString) info.getContent()).getOctets()));
		AuthenticatedSafe authSafe = new AuthenticatedSafe(
				(ASN1Sequence) asn1is.readObject());
		ContentInfo[] c = authSafe.getContentInfo();
		for (int i = 0; i < c.length; i++) {
			ASN1Sequence seq = null;
			if (c[i].getContentType().equals(PKCSObjectIdentifiers.data)) {
				asn1is = new ASN1InputStream(new ByteArrayInputStream(
						((ASN1OctetString) c[i].getContent()).getOctets()));
				seq = (ASN1Sequence) asn1is.readObject();
			} else if (c[i].getContentType().equals(
					PKCSObjectIdentifiers.encryptedData)) {
				EncryptedData ed = new EncryptedData(
						(ASN1Sequence) c[i].getContent());
				PKCS12PBEParams pbeParams = new PKCS12PBEParams(
						(ASN1Sequence) ed.getEncryptionAlgorithm()
								.getParameters());
				ASN1InputStream bis = null;
				bis = new ASN1InputStream(new ByteArrayInputStream(codeData(
						false, ed.getContent().getOctets(), pbeParams,
						password.toCharArray(), new RC2Engine(), 40)));
				seq = (ASN1Sequence) bis.readObject();
			} else {
				continue;
			}
			X509CertificateStructure x509cert = null;
			for (int j = 0; j < seq.size(); j++) {
				SafeBag sb = new SafeBag((ASN1Sequence) seq.getObjectAt(j));
				if (sb.getBagId().equals(PKCSObjectIdentifiers.certBag)) {
					asn1is = new ASN1InputStream(
							new ByteArrayInputStream(
									((DEROctetString) new CertBag(
											(ASN1Sequence) sb.getBagValue())
											.getCertValue()).getOctets()));
					ASN1Sequence xseq = (ASN1Sequence) asn1is.readObject();
					if (xseq.size() > 1
							&& xseq.getObjectAt(1) instanceof DERObjectIdentifier
							&& xseq.getObjectAt(0).equals(
									PKCSObjectIdentifiers.signedData)) {
						x509cert = X509CertificateStructure
								.getInstance(new SignedData(ASN1Sequence
										.getInstance((ASN1TaggedObject) xseq
												.getObjectAt(1), true))
										.getCertificates().getObjectAt(0));
					} else {
						x509cert = X509CertificateStructure.getInstance(xseq);
					}
					CertificateFactory cf = CertificateFactory
							.getInstance("X.509");
					cert = (X509Certificate) cf
							.generateCertificate(new ByteArrayInputStream(
									x509cert.getDEREncoded()));
				} else if (sb.getBagId().equals(
						PKCSObjectIdentifiers.pkcs8ShroudedKeyBag)) {
					EncryptedPrivateKeyInfo encInfo = EncryptedPrivateKeyInfo
							.getInstance((ASN1Sequence) sb.getBagValue());
					PKCS12PBEParams pbeParams = new PKCS12PBEParams(
							(ASN1Sequence) encInfo.getEncryptionAlgorithm()
									.getParameters());
					ASN1InputStream dIn = new ASN1InputStream(
							new ByteArrayInputStream(codeData(false,
									encInfo.getEncryptedData(), pbeParams,
									password.toCharArray(), new DESedeEngine(),
									192)));
					PrivateKeyInfo privKeyInfo = new PrivateKeyInfo(
							(ASN1Sequence) dIn.readObject());
					privateKey = getRSAPrivateKey(privKeyInfo.getPrivateKey()
							.getDEREncoded());
				}
			}
		}
	}

	public PrivateKey getRSAPrivateKey(byte keyder[]) throws Exception {
		PrivateKey prikey = null;
		try {
			ByteArrayInputStream bIn = new ByteArrayInputStream(keyder);
			ASN1InputStream encais = new ASN1InputStream(bIn);
			DERObject encdobj = encais.readObject();
			ASN1Sequence encass = (ASN1Sequence) encdobj;
			DERInteger encdermod = DERInteger
					.getInstance(encass.getObjectAt(1));
			BigInteger mod = encdermod.getValue();
			DERInteger encderexp = DERInteger
					.getInstance(encass.getObjectAt(2));
			BigInteger pubexp = encderexp.getValue();
			BigInteger errpriexp = DERInteger
					.getInstance(encass.getObjectAt(3)).getValue();
			BigInteger primeP = DERInteger.getInstance(encass.getObjectAt(4))
					.getValue();
			BigInteger primeQ = DERInteger.getInstance(encass.getObjectAt(5))
					.getValue();
			BigInteger DP = DERInteger.getInstance(encass.getObjectAt(6))
					.getValue();
			BigInteger DQ = DERInteger.getInstance(encass.getObjectAt(7))
					.getValue();
			BigInteger QmodP = DERInteger.getInstance(encass.getObjectAt(8))
					.getValue();
			encais.close();
			bIn.close();
			bIn = null;
			BigInteger onepar = new BigInteger("1", 16);
			BigInteger priexp = pubexp.modInverse(primeP.subtract(onepar)
					.multiply(primeQ.subtract(onepar)));
			KeySpec prikeyspec = new RSAPrivateCrtKeySpec(mod, pubexp, priexp,
					primeP, primeQ, DP, DQ, QmodP);
			KeyFactory prikeyfact = KeyFactory.getInstance("RSA");
			prikey = prikeyfact.generatePrivate(prikeyspec);

		} catch (Exception e) {
			e.printStackTrace();
		}
		return prikey;
	}

	public X509Certificate getCert() {
		return cert;
	}

	public PrivateKey getPrivateKey() {
		return privateKey;
	}


	private static byte[] getFileByte(String path)
			throws FileNotFoundException, IOException {
		File f = new File(path);
		ByteArrayOutputStream bos = new ByteArrayOutputStream((int)f.length());  
		BufferedInputStream in = null;
		in = new BufferedInputStream(new FileInputStream(f));
		int buf_size = 1024;
		byte[] buffer = new byte[buf_size];
		int len = 0;
		while (-1 != (len = in.read(buffer, 0, buf_size))) {
			bos.write(buffer, 0, len);
		}
		
		return bos.toByteArray();
	}

	/**
	 * 签名
	 * 
	 * @param algtype
	 *            算法类型
	 * @param prikey
	 *            私钥对象
	 * @param data
	 *            签名数据
	 * @return
	 * @throws SecurityException
	 */
	public byte[] externalSign(String algtype, PrivateKey prikey, byte[] data)
			throws SecurityException {
		byte[] result = null;
		Signature signatue = null;
		try {
			signatue = Signature.getInstance(algtype);
			signatue.initSign(prikey);
			signatue.update(data);
			result = signatue.sign();
		} catch (Exception e) {
			throw new SecurityException(e.getMessage());
		}
		return result;
	}

}